
#include <sstream>
#include <iomanip>
#include <iostream>

#include <map>
#include <vector>
#include <string>
#include <ostream>
#include <istream>
#include <cassert>

#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/spirit/core.hpp>
#include <boost/spirit/utility/confix.hpp>
#include <boost/spirit/utility/escape_char.hpp>
#include <boost/spirit/utility/lists.hpp>
#include <boost/spirit/iterator/multi_pass.hpp>

#include "html.h"

namespace html {
namespace parser {

//////////////////////////// Parser ////////////////////////////

using namespace boost::spirit;

struct html_stack {
  html_stack(dom::root_node root): root_(root) {}

  template<class T> void push(T it1, T it2) {
    if (stack_.empty()) {
      root_.tag() = std::string(it1, it2);
      stack_.push_back(root_);
    } else {
      stack_.push_back(stack_.back().add_child(std::string(it1, it2)));
    }
  }
  template<class T> void pop(T it1, T it2) {
    stack_.pop_back();
  }
  template<class T> void add_key(T it1, T it2) {
    key_.assign(it1, it2);
  }
  template<class T> void add_value(T it1, T it2, const char quote) {
    stack_.back().set_attrib(key_, std::string(it1, it2), quote);
  }
  template<class T> void add_text(T it1, T it2) {
    stack_.back() += std::string(it1, it2);
  }
  template<class T> void add_doctype(T it1, T it2) {
	  root_.set_doc_type(std::string(it1, it2));
  }
  template<class T> void add_xmlversion(T it1, T it2) {
    root_.set_xml_version(std::string(it1, it2));
  }
  template<class T> void add_comment(T it1, T it2) {
    stack_.back().add_comment(std::string(it1, it2));
  }

private:
  dom::root_node root_;
  std::string key_;
  std::vector<dom::node> stack_;
};

struct html_grammar : public grammar<html_grammar> {

  html_grammar(html_stack& stack): stack_(&stack) {}

  template<typename ScannerT> struct definition {
  typedef typename ScannerT::iterator_t iterator;

  definition(const html_grammar& self) {
    typedef boost::function<void(iterator, iterator)> str_action;

    str_action push(boost::bind(&html_stack::push<iterator>, self.stack_, _1, _2));
    str_action pop(boost::bind(&html_stack::pop<iterator>, self.stack_, _1, _2));
    str_action add_key(boost::bind(&html_stack::add_key<iterator>, self.stack_, _1, _2));
    str_action add_text(boost::bind(&html_stack::add_text<iterator>, self.stack_, _1, _2));
    str_action add_doctype(boost::bind(&html_stack::add_doctype<iterator>, self.stack_, _1, _2));
    str_action add_xmlversion(boost::bind(&html_stack::add_xmlversion<iterator>, self.stack_, _1, _2));
    str_action add_comment(boost::bind(&html_stack::add_comment<iterator>, self.stack_, _1, _2));
    str_action add_value_single(boost::bind(&html_stack::add_value<iterator>, self.stack_, _1, _2, '\''));
    str_action add_value_double(boost::bind(&html_stack::add_value<iterator>, self.stack_, _1, _2, '"'));

    html_ 
      = !xmlversion_ >> *space_p >> !doctype_ >> *space_p >> html_tag_;

    xmlversion_ 
      = confix_p("<?xml version=\"", (*anychar_p)[ add_xmlversion ], '"' >> *space_p >> "?>");

    doctype_ 
      = confix_p("<!DOCTYPE", (*anychar_p)[ add_doctype ], '>');

    html_tag_ = (
      tag_
        = '<' >> *space_p
          >> (+alnum_p)[ push ]
          >> *space_p >> attributes_
          >> ( str_p("/>")[ pop ] | (content_ >> end_tag_) ),

      content_ 
        = '>' >> *(text_ | html_tag_ | comment_),
            
      end_tag_ 
        = "</" >> *space_p >> (+alnum_p)[ pop ] >> *space_p >> '>'
    );

    attributes_ = (
      attrs_ 
        = *(attr_key_ >> *space_p >> '=' >> *space_p >> attr_value_ >> *space_p),

      attr_key_ 
        = (+(alnum_p | ':' | '-'))[ add_key ],

      attr_value_
        = confix_p('"', (*anychar_p)[ add_value_double ], '"') 
	      | confix_p('\'', (*anychar_p)[ add_value_single ], '\'') 
    );

    comment_
      = confix_p("<!--", (*anychar_p)[ add_comment ], "-->");

    text_
      = (+(anychar_p - '<'))[ add_text ];
  }

  subrule<0>  attrs_;
  subrule<1>  attr_key_;
  subrule<2>  attr_value_;

  subrule<3>  tag_;
  subrule<4>  end_tag_;
  subrule<5>  content_;

  rule<ScannerT> html_, html_tag_, attributes_, text_, doctype_, xmlversion_, comment_;

  const rule<ScannerT>& start() const { return html_; }
};

private:
    html_stack* stack_;
};


bool read(const std::string& s, dom::root_node root) {
  html_stack stack(root);
  html_grammar grammar(stack);
  parse_info<> info = parse(s.c_str(), grammar);
  return info.full;
}

bool read(std::istream& is, dom::root_node root) {
  html_stack stack(root);
  html_grammar grammar(stack);

  is.unsetf(std::ios::skipws);

  typedef multi_pass< std::istream_iterator<char> > iterator_t;
  iterator_t begin(make_multi_pass(std::istream_iterator<char>(is)));
  iterator_t end(make_multi_pass(std::istream_iterator<char>()));

  return parse(begin, end, grammar).hit;
}

}
} 
 
